package jam4j.lang;

import java_cup.runtime.ComplexSymbolFactory;
import java_cup.runtime.ComplexSymbolFactory.ComplexSymbol;
import java_cup.runtime.ComplexSymbolFactory.Location;

import jam4j.util.Parsing;

import static jam4j.lang.TokenType.*;
%%

%class Lexer
%final
%cupsym TokenType
%cup
%type ComplexSymbol
%eofval{
  return symbol("EOF", TokenType.EOF);
%eofval}

%line
%column

%unicode
%state KEYWORDS PUNCTONLY
%xstate ACTION_CODE

%{
    Lexer(java.io.InputStream in, String filename) {
        this(in);
        this.filename = filename;
    }

    private final ComplexSymbolFactory symbolFactory =
        new ComplexSymbolFactory();
    private final StringBuilder builder = new StringBuilder();
    private String filename = "<unknown>";
    private int actionNesting = 0;
    private Location actionBlockStart;

    private ComplexSymbol symbol(String name, int type) {
        return symbol(name, type, null);
    }

    private ComplexSymbol symbol(String name, int type, Object value) {
        final Location start = new Location(filename, yyline, yycolumn);

        return (ComplexSymbol) symbolFactory.newSymbol(name, type,
            start, Parsing.afterText(start, yytext()), value);
    }

    private String handleEscapes() {
        final java.util.StringTokenizer tokenizer =
            new java.util.StringTokenizer(yytext(), "\\\"", true);
        final StringBuilder builder = new StringBuilder();
        boolean escaped = false;
        
        while (tokenizer.hasMoreTokens()) {
            final String token = tokenizer.nextToken();
            
            if ("\\".equals(token))
                if (escaped) {
                    builder.append('\\');
                    escaped = false;
                } else
                    escaped = true;
            else if ("\"".equals(token)) {
                if (escaped) {
                    builder.append('\"');
                    escaped = false;
                }
            } else {
                builder.append(token);
                escaped = false;
            }
        }
    
        return builder.toString();
    }

    void keywordMode() {
        yybegin(KEYWORDS);
    }

    void punctOnlyMode() {
        yybegin(PUNCTONLY);
    }

    void actionBlockMode() {
        actionNesting = 1;
        actionBlockStart = new Location(filename, yyline, yycolumn);
        yybegin(ACTION_CODE);
    }

    ComplexSymbolFactory symbolFactory() {
        return symbolFactory;
    }

    String filename() {
        return filename;
    }
%}

%init{
    keywordMode();
%init}

EndOfLine = \r\n | [\r\n\u2028\u2029\u000B\u000C\u0085]
Whitespace = [ \t] | {EndOfLine}
LineComment = # [^\r\n\u2028\u2029\u000B\u000C\u0085]* {EndOfLine}
Quoted = \" ([^\\\"] | \\ [^])* \"
ArgChar = [^ \t\r\n\u2028\u2029\u000B\u000C\u0085\\\"] | \\ [^] | {Quoted}
// ArgStart = [^:;\\\" \t\r\n\u2028\u2029\u000B\u000C\u0085] | \\ [^] | {Quoted} | [:;] {ArgChar}
// Arg = {ArgStart} {ArgChar}*
Arg = {ArgChar}+

%%

// Skippables

{Whitespace}+ { }
{LineComment} { }

// Operators

";"     { return symbol("SEMIC", SEMIC); }
":"     { return symbol("COLON", COLON); }

"["     { return symbol("LBRACK", LBRACK); }
"]"     { return symbol("RBRACK", RBRACK); }
"("     { return symbol("LPAREN", LPAREN); }
")"     { return symbol("RPAREN", RPAREN); }
"{"     { return symbol("LBRACE", LBRACE); }
"}"     { return symbol("RBRACE", RBRACE); }

"="     { return symbol("EQUAL", EQUAL); }
"+="    { return symbol("PLUS_EQUAL", PLUS_EQUAL); }
"?="    { return symbol("QUESTION_EQUAL", QUESTION_EQUAL); }

"<"     { return symbol("LANGLE", LANGLE); }
"<="    { return symbol("LANGLE_EQUAL", LANGLE_EQUAL); }
"!="    { return symbol("BANG_EQUAL", BANG_EQUAL); }
">"     { return symbol("RANGLE", RANGLE); }
">="    { return symbol("RANGLE_EQUAL", RANGLE_EQUAL); }

"!"     { return symbol("BANG", BANG); }

"|"     { return symbol("BAR", BAR); }
"||"    { return symbol("BARBAR", BARBAR); }
"&"     { return symbol("AMPER", AMPER); }
"&&"    { return symbol("AMPERAMPER", AMPERAMPER); }

// Keywords

<KEYWORDS> {
    "include"   { return symbol("INCLUDE", INCLUDE); }
    "local"     { return symbol("LOCAL", LOCAL); }
    "rule"      { return symbol("RULE", RULE); }
    "actions"   { return symbol("ACTIONS", ACTIONS); }

    "on"        { return symbol("ON", ON); }
    "break"     { return symbol("BREAK", BREAK); }
    "continue"  { return symbol("CONTINUE", CONTINUE); }
    "return"    { return symbol("RETURN", RETURN); }
    "for"       { return symbol("FOR", FOR); }
    "in"        { return symbol("IN", IN); }
    "switch"    { return symbol("SWITCH", SWITCH); }
    "case"      { return symbol("CASE", CASE); }
    "if"        { return symbol("IF", IF); }
    "else"      { return symbol("ELSE", ELSE); }
    "while"     { return symbol("WHILE", WHILE); }

    "bind"      { return symbol("BIND", BIND); }
    "updated"   { return symbol("UPDATED", UPDATED); }
    "together"  { return symbol("TOGETHER", TOGETHER); }
    "ignore"    { return symbol("IGNORE", IGNORE); }
    "quietly"   { return symbol("QUIETLY", QUIETLY); }
    "piecemeal" { return symbol("PIECEMEAL", PIECEMEAL); }
    "existing"  { return symbol("EXISTING", EXISTING); }
    "maxline"   { return symbol("MAXLINE", MAXLINE); }
}

// Literal

{Arg}   { return symbol("ARG", ARG, handleEscapes()); }

// Action Block

<ACTION_CODE> {
    [^{}]+  { builder.append(yytext()); }
    "{"     { actionNesting++; builder.append('{'); }

    "}" {
        if (--actionNesting == 0) {
            keywordMode();

            final String actionBlock = builder.toString();
            final ComplexSymbol ans = (ComplexSymbol)
                symbolFactory.newSymbol("ACTION_BLOCK", ACTION_BLOCK,
                    actionBlockStart,
                    Parsing.afterText(actionBlockStart, actionBlock),
                    actionBlock);
            builder.setLength(0);
            actionBlockStart = null;
            yypushback(1);
            return ans;
        } else
            builder.append('}');
    }
}

// Error

[^] {
    throw new RuntimeException("Unexpected character <" + yytext() + ">");
}

// vim: ft=jflex
